#include "console.hpp"
#include "ConsoleApp.hpp"
#include "engine.hpp"
#include <cstring>
//#include <cctype>
#include <locale>
#include <iostream>

namespace wolf
{
using namespace std;
Console::Console(unsigned nLines, unsigned nCols, Font *font_) :
		color(224, 224, 224)
{
	linesCount = nLines;
	columnsCount = nCols;
	font = font_;
	cursorLine = 0;
	cursorColumn = 0;
	listening = true;
	showing = true;
	input = 0;
	outbuffer.resize(nLines, NULL);
	offsetX = offsetY = 0;
	WOLF_FOR( deque<_CharInfo*>, outbuffer, it )
	{
		_CharInfo *n = new _CharInfo[columnsCount];
//            memset(n, 0, columnsCount * sizeof(_CharInfo));
		*it = n;
	}

	consoleLock = SDL_CreateMutex();
	appThread = NULL;
}

Console::~Console(void)
{
	killApp();
}

//    VARGETTER( Console, bool, isListening )
//    VARSETTER( Console, bool, setlistening )
//    VARGETTER( Console, bool, isShowing )
//    VARSETTER( Console, bool, setShowing )
//    VARGETTER( Console, unsigned, getNumLines )
//    VARGETTER( Console, unsigned, getNumCols )
//    VARGETTER( Console, int, getOffsetX )
//    VARSETTER( Console, int, setOffsetX )
//    VARGETTER( Console, int, getOffsetY )
//    VARSETTER( Console, int, setOffsetY )
//    VARGETTER( Console, unsigned, getLine )
//    VARSETTER( Console, unsigned, setLine )
//    VARGETTER( Console, unsigned, getCol )
//    VARSETTER( Console, unsigned, setCol )
//    VARGETTER( Console, Font *, getFont )
//    VARSETTER( Console, Font *, setFont )
//    VARGETTER( Console, int, getInput )
//    VARSETTER( Console, int, setInput )
//
//
//    OBJECTGETTER( Console, Color, getColor )
//    OBJECTSETTER( Console, Color, setColor )

void Console::onDraw(Engine &engine, SDL_Surface *dst)
{
	if (showing)
	{
		SDL_mutexP(consoleLock); // Lock
		Uint16 w = font->fontLength(), h = font->fontSize();
		unsigned x = 0, y = 0;
		//Drawing the text
		for (unsigned i = 0; i < linesCount; ++i)
		{
			for (unsigned j = 0; j < columnsCount; ++j)
			{
				font->writeChar(outbuffer[i][j].character, dst, x + offsetX, y + offsetY, outbuffer[i][j].color);
				x += w;
			}
			y += h;
			x = 0;
		}
		//Drawing the carret
		if ((engine.getCurrentFrame() >> 2) % 2)
		{
			SDL_Rect carret =
			{ short(cursorColumn * w + offsetX), short(cursorLine * h + offsetY), w, h };
			SDL_FillRect(dst, &carret, color.toPixel(dst->format));
		}
		SDL_mutexV(consoleLock); // Unlock
	}
}

bool Console::onEvent(Engine &engine, SDL_Event &event)
{
	if (listening && event.type == SDL_KEYDOWN)
	{
		int c = event.key.keysym.unicode;
		if (c >= ' ')
		{
			SDL_mutexP(consoleLock); // Lock
			input = c;
			SDL_mutexV(consoleLock); // Unlock
		}
		else if (event.key.keysym.sym == SDLK_RETURN)
		{
			SDL_mutexP(consoleLock); // Lock
			input = '\n';
			SDL_mutexV(consoleLock); // Unlock
		}
		else if (event.key.keysym.sym == SDLK_BACKSPACE)
		{
			SDL_mutexP(consoleLock); // Lock
			input = '\b';
			SDL_mutexV(consoleLock); // Unlock
		}
		else
			return true;
		return false;
	}
	return true;
}

void Console::onStartFrame(Engine &engine)
{
//        SDL_mutexP( _consoleLock); // Lock
}
void Console::onFrame(Engine &engine)
{
//        SDL_mutexV( _consoleLock); // Unlock
}

void Console::write(int c)
{
	if (c == '\n')
	{
		cursorColumn = 0;
		cursorLine++;
		while (cursorLine >= linesCount)
		{
			newLine();
			--cursorLine;
		}
	}
	else
	{
		if (cursorColumn >= columnsCount)
		{
			cursorColumn = 0;
			cursorLine++;
		}
		while (cursorLine >= linesCount)
		{
			newLine();
			--cursorLine;
		}
		outbuffer[cursorLine][cursorColumn].character = c;
		outbuffer[cursorLine][cursorColumn].color = color;
		++cursorColumn;
	}
}

void Console::write(const char *s)
{
	for (const char *it = s; *it != 0; ++it)
		write(*it);
}

void Console::write(const string &s)
{
	write(s.c_str());
}

void Console::clear(void)
{
	WOLF_FOR( deque<_CharInfo*>, outbuffer, it )
	{
//            delete [] *it;
//            *it = new _CharInfo[columnsCount];
		for (unsigned j = 0; j < columnsCount; ++j)
		{
			(*it)[j].character = 0;
			(*it)[j].color = color;
		}
		cursorLine = cursorColumn = 0;
	}
}

void Console::newLine(void)
{
	delete[] outbuffer.front();
	outbuffer.pop_front();
	outbuffer.push_back(new _CharInfo[columnsCount]);
}

int appCaller(void * data)
{
	ConsoleApp *app = reinterpret_cast<ConsoleApp*>(data);
	vector<string> params;
	return app->run(params);
}

bool Console::callApp(ConsoleApp &app)
{
	if (appThread)
		return false; // It Already has a running app;
	app.console = this;
	appThread = SDL_CreateThread(appCaller, (void*) ((&app)));
	return true;
}
void Console::killApp(void)
{
	if (appThread == NULL)
		return;
	SDL_KillThread(appThread);
	appThread = NULL;
}

int Console::waitApp(void)
{
	if (appThread == NULL)
		return -1;
	int status;
	SDL_mutexP(consoleLock); //Lock
	input = -1;
	SDL_mutexV(consoleLock); //unlock
	SDL_WaitThread(appThread, &status);
	appThread = NULL;
	return status;
}

ConsoleMessage *Console::receiveMessage(void)
{
	ConsoleMessage *message = NULL;
	SDL_mutexP(consoleLock); //Lock
	if (!mainQueue.empty())
	{
		message = mainQueue.front();
		mainQueue.pop();
	}
	SDL_mutexV(consoleLock); //unlock
	return message;
}

void Console::sendMessage(ConsoleMessage *message)
{
	SDL_mutexP(consoleLock); // Lock
	appQueue.push(message);
	SDL_mutexV(consoleLock); // Unlock
}

unsigned Console::getColumnsCount() const
{
	return columnsCount;
}

unsigned Console::getCursorColumn() const
{
	return cursorColumn;
}

unsigned Console::getCursorLine() const
{
	return cursorLine;
}

Font *Console::getFont() const
{
	return font;
}

int Console::getInput() const
{
	return input;
}

unsigned Console::getLinesCount() const
{
	return linesCount;
}

int Console::getOffsetX() const
{
	return offsetX;
}

int Console::getOffsetY() const
{
	return offsetY;
}

bool Console::isListening() const
{
	return listening;
}

bool Console::isShowing() const
{
	return showing;
}

void Console::setCursorColumn(unsigned cursorColumn)
{
	this->cursorColumn = cursorColumn;
}

void Console::setCursorLine(unsigned cursorLine)
{
	this->cursorLine = cursorLine;
}

void Console::setFont(Font *font)
{
	this->font = font;
}

void Console::setInput(int input)
{
	this->input = input;
}

void Console::setListening(bool listening)
{
	this->listening = listening;
}

void Console::setOffsetX(int offsetX)
{
	this->offsetX = offsetX;
}

void Console::setOffsetY(int offsetY)
{
	this->offsetY = offsetY;
}

void Console::setShowing(bool showing)
{
	this->showing = showing;
}

const Color & Console::getColor(void) const
{
	return color;
}

void Console::setColor(const Color & value)
{
	color = value;
}

}
